package chess;

import java.util.ArrayList;
import java.util.Random;

import game.ChessGame;
import game.Game;

public class BetterAIChessPlayer extends AIPlayer {

	private Random randomizer;
	private static final int PAWN_VALUE=1;
	private static final int BISHOP_VALUE=3;
	private static final int ROOK_VALUE=5;
	private static final int QUEEN_VALUE=9;
	private static final int KNIGHT_VALUE=3;
	
	private static final int CHECKMATED_VALUE=-2000;
	private static final int CHECKMATING_VALUE=2000;
	private static final int STALEMATE_VALUE=-1000;
	private static final int NO_MOVES=-1000;
	
	private static final int VERY_BIG_NUMBER=99999;
	
	private static final int RECURSION_DEPTH=2; //2 makes turns take a reasonable amount of time, but
												//doesn't seem to play very well.  3 Is a bit long to
												//wait, but it seems to play a bit better.
	
	//This class will be used to organize and sort the possible moves
	class MoveChoice{
		private command.Command movement;
		private int 			   score;
		
		public MoveChoice(command.Command theMove){
			movement=theMove;
		}
		
		public command.Command getMove(){
			return movement;
		}
		
		public int getScore(){
			return score;
		}
		
		public void setScore(int newScore){
			this.score=newScore;
		}
		
		public String toString(){
			return movement.toString();
		}
	}
	
	
	
	public BetterAIChessPlayer(String name,Game game, String colour) {
		super(name, game, colour);
		randomizer=new Random();
	}
	
	//Returns a positive value for this players advantage, negative for other player advantage.
	private int evaluate(Board b,Player curPlayer){
		int totalValue=0;
		game.ChessGame cg = (game.ChessGame)this.getGame();
		Player opponentPlayer=cg.getOpponent(curPlayer);
		
		//Special situations
		if (cg.testForCheckMate(curPlayer)){
			return CHECKMATED_VALUE;  //You really don't want to be in checkmate
		}
		if (cg.testForStaleMate(curPlayer)){
			return STALEMATE_VALUE; //Try not to be in stalemate
		}
		if (cg.testForCheckMate(opponentPlayer)){
			return CHECKMATING_VALUE; //Really go for checkmating the opponent.
		}
		
		for (Piece pieceHere:curPlayer.getMyPieces()){
				if (!pieceHere.isDead()){
						//Your piece
						if (pieceHere instanceof PawnPiece){
							totalValue+=BetterAIChessPlayer.PAWN_VALUE;
						}
						else if (pieceHere instanceof BishopPiece){
							totalValue+=BetterAIChessPlayer.BISHOP_VALUE;
						}
						else if (pieceHere instanceof RookPiece){
							totalValue+=BetterAIChessPlayer.ROOK_VALUE;
						}
						else if (pieceHere instanceof QueenPiece){
							totalValue+=BetterAIChessPlayer.QUEEN_VALUE;
						}
						else if (pieceHere instanceof KnightPiece){
							totalValue+=BetterAIChessPlayer.KNIGHT_VALUE;
						}
				}
		}
		for (Piece pieceHere:opponentPlayer.getMyPieces()){
				if (!pieceHere.isDead()){
						//Enemy Pieces
						if (pieceHere instanceof PawnPiece){
							totalValue-=BetterAIChessPlayer.PAWN_VALUE;
						}
						else if (pieceHere instanceof BishopPiece){
							totalValue-=BetterAIChessPlayer.BISHOP_VALUE;
						}
						else if (pieceHere instanceof RookPiece){
							totalValue-=BetterAIChessPlayer.ROOK_VALUE;
						}
						else if (pieceHere instanceof QueenPiece){
							totalValue-=BetterAIChessPlayer.QUEEN_VALUE;
						}
						else if (pieceHere instanceof KnightPiece){
							totalValue-=BetterAIChessPlayer.KNIGHT_VALUE;
						}
				}
		}
		return totalValue;
	}

	
	private void heapSort(ArrayList<MoveChoice> moves){
		makeHeap(moves);
		MoveChoice temp;
		int end = moves.size()-1;
		while (end>0){
			
			//Swap
			temp = moves.get(0);
			moves.set(0, moves.get(end));
			moves.set(end, temp);
			
			siftHeap(moves,0,end-1);
			
			end-=1;
			
		}
	}
	
	private void makeHeap(ArrayList<MoveChoice> moves){
		int start=(moves.size()-2)/2;
		
		while (start>=0){
			siftHeap(moves,start,moves.size()-1);
			start-=1;
		}
	}
	
	private void siftHeap(ArrayList<MoveChoice> moves,int startPoint,int endPoint){
		int root = startPoint;
		int child;
		MoveChoice temp;
		
		while ((root*2+1)<=endPoint){
			child = root*2 +1;
			
			if ((child+1 <= endPoint)&&(moves.get(child).getScore())<(moves.get(child+1).getScore())){
				child = child +1;
			}
			if ((child<=endPoint)&&(moves.get(root).getScore()<moves.get(child).getScore())){
				//Swap
				temp = moves.get(root);
				moves.set(root, moves.get(child));
				moves.set(child, temp);
				
				root=child;
			}
			else{
				return;
			}
		}
	}
	
	
	
	/**
	 * This move scorer uses Alpha-Beta pruning to score the effectiveness of different moves
	 * I used some examples from:  http://www.xs4all.nl/~verhelst/chess/search.html
	 * to understand and implement this function.
	 * 
	 * @param move   The initial move to judge
	 * @param depth  How many turns into the future to look
	 * @param alpha  Constraints on how far away from the good value to bother looking
	 * @param beta   Constraints on how far away from the good value to bother looking
	 * @return
	 */
	private int moveScorer(MoveChoice move, int depth, int alpha, int beta){
		
		int best=-BetterAIChessPlayer.VERY_BIG_NUMBER;
		int value;
		game.ChessGame curGame= (game.ChessGame)this.getGame();
		Player player=null;
		
		if (depth%2==0){
			player=this;
		}
		else{
			player=curGame.getOpponent(this);
		}
		
		ArrayList<MoveChoice> successors = new ArrayList<MoveChoice>();
		MoveChoice pos;
		
		ArrayList<Tile> movementTiles=null,attackTiles=null;
		Piece p;
		
		if (depth==0){
			move.getMove().execute();
			int returnValue = evaluate(this.getGame().getBoard(),player);
			move.getMove().undo();
			return returnValue;
		}
		
		move.getMove().execute();
		//Get all possible moves, and add them to Successors
		for(int i=0;i<player.getMyPieces().size();i++){
			p=player.getMyPieces().get(i);
			if (!(p.isDead())){  //Only bother with living pieces
				//Get the moves and attacks
				movementTiles=p.getMovementTiles();
				attackTiles=curGame.findAttackMoves(movementTiles,player);
				curGame.addSpecialChessMoves(p.getCurrentTile(),player,movementTiles,attackTiles); //Should go here, after attackTiles, and before bad moves are filtered
				curGame.filterBadMoves(p.getCurrentTile(),player,movementTiles,attackTiles); //Filter out things that would put you in

				
				//Add the moves to the list of possible moves
				for (Tile t:movementTiles){
					successors.add(new MoveChoice(new command.MoveCommand(p.getCurrentTile(),t)));
				}
				for (Tile t:attackTiles){
					if (t.isPieceHere()){
						successors.add(new MoveChoice(new command.AttackCommand(p.getCurrentTile(),t,player)));
					}
					else{
						// En passant attack
						successors.add(new MoveChoice(new command.EnPassantAttackCommand(p.getCurrentTile(),t,player)));
					}
				}	
			}
		}  //End of getting moves
		
		
		while(!successors.isEmpty() && best<beta){
			pos=successors.remove(successors.size()-1); //Remove the last element (Just so it doesn't have to shift the array around)
			if (best>alpha){
				alpha=best;
			}
			value = -moveScorer(pos,depth-1,-beta,-alpha);
			if(value>best){
				best=value;
			}
		}
		move.getMove().undo();
		return best;
		
		
	}
	
	public void takeTurn(){
		ArrayList<MoveChoice> possibleMoves = new ArrayList<MoveChoice>();
		ArrayList<Tile> movementTiles=null,attackTiles=null;
		game.ChessGame curGame= (game.ChessGame)this.getGame();
		command.Command possibleMove;
		MoveChoice newMoveChoice;
		Piece p;
		
		for(int i=0;i<this.getMyPieces().size();i++){
			p=this.getMyPieces().get(i);
			if (!(p.isDead())){  //Only bother with living pieces
				//Get the moves and attacks
				movementTiles=p.getMovementTiles();
				attackTiles=curGame.findAttackMoves(movementTiles,this);
				curGame.addSpecialChessMoves(p.getCurrentTile(),this,movementTiles,attackTiles); //Should go here, after attackTiles, and before bad moves are filtered
				curGame.filterBadMoves(p.getCurrentTile(),this,movementTiles,attackTiles); //Filter out things that would put you in

				
				//Add the moves to the list of possible moves
				//for (Tile t:movementTiles){
				for (int j=0;j<movementTiles.size();j++){
					Tile t=movementTiles.get(j);
					possibleMove=new command.MoveCommand(p.getCurrentTile(),t);
					newMoveChoice=new MoveChoice(possibleMove);
					newMoveChoice.setScore(moveScorer(newMoveChoice,BetterAIChessPlayer.RECURSION_DEPTH*2,-BetterAIChessPlayer.VERY_BIG_NUMBER,BetterAIChessPlayer.VERY_BIG_NUMBER));
					possibleMoves.add(newMoveChoice);
				}
				for (int j=0;j<attackTiles.size();j++){
					Tile t=attackTiles.get(j);
					if (t.isPieceHere()){
						possibleMove=new command.AttackCommand(p.getCurrentTile(),t,this);
						newMoveChoice=new MoveChoice(possibleMove);
						newMoveChoice.setScore(moveScorer(newMoveChoice,BetterAIChessPlayer.RECURSION_DEPTH*2,-BetterAIChessPlayer.VERY_BIG_NUMBER,BetterAIChessPlayer.VERY_BIG_NUMBER));
						possibleMoves.add(newMoveChoice);
					}
					else{
						// En passant attack
						possibleMove=new command.EnPassantAttackCommand(p.getCurrentTile(),t,this);
						newMoveChoice=new MoveChoice(possibleMove);
						newMoveChoice.setScore(moveScorer(newMoveChoice,BetterAIChessPlayer.RECURSION_DEPTH*2,-BetterAIChessPlayer.VERY_BIG_NUMBER,BetterAIChessPlayer.VERY_BIG_NUMBER));
						possibleMoves.add(newMoveChoice);
					}
				}	
			}
		}	
		
		//Sort the moves by score, best(Highest) at position 0
		heapSort(possibleMoves);
		
		//Do the best one
		possibleMoves.get(0).getMove().execute();
		curGame.listOfMoves.add(possibleMoves.get(0).getMove()); //And add it to the list of moves
		
		//Check to see if the move was a castle
		if (possibleMoves.get(0).getMove() instanceof command.MoveCommand){
			command.MoveCommand lastMove=(command.MoveCommand)(possibleMoves.get(0).getMove());
			Piece piece = lastMove.getDestTile().getPieceHere();
			if (piece instanceof KingPiece){
				if ((lastMove.getDestTile().getX()==ChessGame.CASTLE_COLUMN_1)||(lastMove.getDestTile().getX()==ChessGame.CASTLE_COLUMN_2)){
					if(!lastMove.hadMovedPreviously()){
						curGame.finishCastleMove(this,lastMove.getDestTile());//Castle
					}
				}
		}
		}

		
		//Check if it is a pawn in the last row, if so, queen it
		if ((possibleMoves.get(0).getMove() instanceof command.MoveCommand)||((possibleMoves.get(0).getMove() instanceof command.AttackCommand))){
			Tile dest;
			Piece piece;
			if (possibleMoves.get(0).getMove() instanceof command.MoveCommand){
				command.MoveCommand lastMove=(command.MoveCommand)possibleMoves.get(0).getMove();
				dest=lastMove.getDestTile();
				piece=dest.getPieceHere();
			}
			else{
				command.AttackCommand lastMove=(command.AttackCommand)possibleMoves.get(0).getMove();
				dest=lastMove.getDestTile();
				piece=dest.getPieceHere();
			}
			if (piece instanceof PawnPiece){
				if (dest.getY() == ChessGame.BLACK_ROW || dest.getY()== ChessGame.WHITE_ROW){
					curGame.queenMe(dest,this);
				}
			}
		}
	
	}
	
}
